package gulimall.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import gulimall.constant.OrderConstant;
import gulimall.entity.Order;
import gulimall.feign.CartFeignService;
import gulimall.feign.MemberFeignService;
import gulimall.feign.WareFeignService;
import gulimall.handler.LoginUserInterceptor;
import gulimall.mapper.OrderMapper;
import gulimall.pojo.vo.MemberAddressVO;
import gulimall.pojo.vo.OrderConfirmVO;
import gulimall.pojo.vo.OrderItemVO;
import gulimall.service.IOrderService;
import gulimall.utils.R;
import gulimall.vo.MemberRespVO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author HLF
 * @since 2022-04-23
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Resource
    private MemberFeignService memberFeignService;

    @Resource
    private CartFeignService cartFeignService;

    @Resource
    private WareFeignService wareFeignService;

    @Resource
    private ThreadPoolExecutor executor;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource(name = "redisTemplate")
    private ValueOperations<String, String> valueOps;

    /**
     * 多次远程调用使用线程池的异步编排
     * 确认订单
     */
    @Override
    public OrderConfirmVO confirmOrder() throws ExecutionException, InterruptedException {
        System.out.println("OrderServiceImpl主线程id：" + Thread.currentThread().getId());

        ThreadLocal<MemberRespVO> threadLocal = LoginUserInterceptor.threadLocal;
        MemberRespVO memberRespVO = threadLocal.get();

        OrderConfirmVO orderConfirmVO = new OrderConfirmVO();

        //在异步任务执行之前先共享数据
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        //1.异步任务1：远程调用查询当前member的地址列表
        CompletableFuture<Void> addressTask = CompletableFuture.runAsync(() -> {
            // 在主线程中拿到原来的数据，在父线程里面共享RequestContextHolder
            // 每一个线程都来共享之前的请求数据
            // 只有共享，拦截其中才会有数据
            RequestContextHolder.setRequestAttributes(requestAttributes);

            System.out.println("OrderServiceImpl-查询member的地址列表线程id：" + Thread.currentThread().getId());

            R r = memberFeignService.addressList(memberRespVO.getId());
            List<MemberAddressVO> addressVOList = r.get("data", new TypeReference<List<MemberAddressVO>>() {
            });
            orderConfirmVO.setAddress(addressVOList);
        }, executor);


        //2.异步任务2：远程调用查询所有选中得到购物项(可以不传用户id，让购物车拦截器拦截，然后购物车拦截器里面获取用户id)
        CompletableFuture<Void> orderItemTask = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);

            System.out.println("OrderServiceImpl-查询所有选中的购物项线程id：" + Thread.currentThread().getId());

            R checkItem = cartFeignService.getCheckItem(memberRespVO.getId());
            List<OrderItemVO> orderItemVOList = checkItem.get("data", new TypeReference<List<OrderItemVO>>() {
            });
            orderConfirmVO.setItem(orderItemVOList);
        }, executor);

        //3.远程调用查询库存(基于第二步，查询所有选中的购物项是否有库存)
        //线程的串行化,在异步任务2执行之后执行
        orderItemTask.thenRunAsync(() -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);

            System.out.println("OrderServiceImpl-查询库存线程id：" + Thread.currentThread().getId());

            //获取所有已经选中的购物项
            List<OrderItemVO> confirmVOItem = orderConfirmVO.getItem();
            List<Long> skuIds = confirmVOItem.stream().map(OrderItemVO::getSkuId).collect(Collectors.toList());

            //远程调用仓储服务获取skuId对应是否有库存，返回Map<Long skuId,boolean hasStock>
            R r = wareFeignService.hasStockMapBySkuIds(skuIds);
            HashMap<Long, Boolean> hasStockMap = r.get("data", new TypeReference<HashMap<Long, Boolean>>() {
            });
            //设置所有被选中的购物项是否有库存
            orderConfirmVO.setStocks(hasStockMap);


        }, executor);

        //合并两个异步任务
        CompletableFuture.allOf(addressTask, orderItemTask).get();


        //4.获取用户的积分
        Integer integration = memberRespVO.getIntegration();
        orderConfirmVO.setIntegration(integration);


        //5.提交订单幂等性解决
        /**
         * 接口幂等性就是用户对同一操作发起的一次请求和多次请求结果是一致的
         * 不会因为多次点击而产生了副作用，比如支付场景，用户购买了商品，支付扣款成功，
         * 但是返回结果的时候出现了网络异常，此时钱已经扣了，用户再次点击按钮，
         * 此时就会进行第二次扣款，返回结果成功，用户查询余额发现多扣钱了，
         * 流水记录也变成了两条。。。这就没有保证接口幂等性
         */
        // 先是在页面中生成一个随机码把他叫做token先存到redis中，然后放到对象中在页面进行渲染。
        // 用户提交表单的时候，带着这个token和redis里面去匹配如果一直那么可以执行下面流程。
        // 匹配成功后再redis中删除这个token，下次请求再过来的时候就匹配不上直接返回
        // 生成防重令牌
        String token = UUID.randomUUID().toString();
        valueOps.set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVO.getId(),
                token, 30, TimeUnit.SECONDS);

        orderConfirmVO.setOrderToken(token);

        return orderConfirmVO;
    }
}
